const fs = require("fs");
const path = require("path");
const EPub = require("../utils/epub");
const xml2js = require("xml2js").parseString;
const {
  MIME_TYPE_EPUB,
  UPLOAD_URL,
  UPLOAD_PATH,
} = require("../utils/constant");

class Book {
  constructor(file, data) {
    // file 刚上传1个文件； data 插入或更新1个文件
    if (file) {
      this.createBookFromFile(file);
    } else {
      this.createBookFromData(data);
    }
  }
  createBookFromFile(file) {
    console.log("createBookFromFile", file);
    const {
      destination,
      filename,
      mimetype = MIME_TYPE_EPUB,
      path,
      originalname,
    } = file;
    const suffix = mimetype === MIME_TYPE_EPUB ? ".epub" : ""; // 后缀名
    const oldBookPath = path; //电子书当前路径
    const bookPath = `${destination}/${filename}${suffix}`; //新路径
    const url = `${UPLOAD_URL}book/${filename}${suffix}`; //下载路径
    const unzipPath = `${UPLOAD_PATH}/unzip/${filename}`; // 解压后
    const unzipUrl = `${UPLOAD_URL}unzip/${filename}`;
    // 创建 unzip后的目录
    if (!fs.existsSync(unzipPath)) {
      fs.mkdirSync(unzipPath, { recursive: true }); //https://www.itranslater.com/qa/details/2325705639944782848 递归创建目录
    }
    //重命名文件
    if (fs.existsSync(oldBookPath) && !fs.existsSync(bookPath)) {
      fs.renameSync(oldBookPath, bookPath);
    }
    //其他属性
    this.fileName = filename; // filename是唯一的，所以拿他做主键
    this.path = `/book/${filename}${suffix}`; // 相对路径
    this.filePath = this.path; // 别名
    this.unzipPath = `/unzip/${filename}`; //解压后 相对路径
    this.unzipUrl = unzipUrl;
    this.url = url; // 下载地址
    this.title = ""; // 书名
    this.author = ""; // 作者
    this.publisher = ""; //出版社
    this.contents = []; //目录
    this.cover = ""; //封面图片url
    this.coverPath = ""; //封面图片路径
    this.category = -1; //电子书分类 id
    this.categoryText = ""; //分类名称
    this.language = ""; //语种
    this.originalName = originalname; // 上传文件原名
    this.contentsTree = []; //树状目录结构
  }
  // 从 post data 构造对象
  createBookFromData(data) {
    this.fileName = data.fileName;
    this.cover = data.coverPath;
    this.title = data.title;
    this.author = data.author;
    this.publisher = data.publisher;
    this.bookId = this.fileName;
    this.language = data.language;
    this.rootFile = data.rootFile;
    this.originalName = data.originalName;
    this.path = data.path || data.filePath;
    this.filePath = this.path;
    this.unzipPath = data.unzipPath;
    this.coverPath = data.coverPath;
    this.createUser = data.username;
    this.createDt = new Date().getTime(); //毫秒
    this.updateDt = this.createDt;
    this.updateType = data.updateType === 0 ? data.updateType : 1; //图书来源
    this.category = data.category || 99; //书类目id
    this.categoryText = data.categoryText || "自定义";
    this.contents = data.contents || [];
  }
  getContents() {
    return this.contents;
  }

  parse() {
    return new Promise((resolve, reject) => {
      const bookPath = `${UPLOAD_PATH}/${this.filePath}`;
      // 检查目录是否存在
      if (!fs.existsSync(bookPath)) {
        reject(new Error("电子书不存在"));
      }
      // 使用方法见 https://github.com/julien-c/epub
      // 使用epub库 解析文件
      const epub = new EPub(bookPath);
      //定义 error 事件的处理方式
      epub.on("error", (err) => {
        reject(err);
      });
      epub.on("end", (err) => {
        // epub is initialized now
        if (err) {
          reject(err);
        } else {
          // console.log(epub.metadata);
          // 检查收到的数据后，对数据做处理；空值等
          const {
            creator,
            creatorFileAs,
            publisher,
            title,
            language,
            cover,
          } = epub.metadata;
          if (!title) {
            reject(new Error("图书标题为空"));
          } else {
            this.title = title;
            this.language = language || "en";
            this.author = creator || creatorFileAs || "unknown";
            this.publisher = publisher || "unknown";
            this.rootFile = epub.rootFile; // 获取 content.opf
            //定义 getImage的回调函数，保存 buffer 到文件
            const handleGetImage = (err, file, mimeType) => {
              if (err) {
                reject(err);
              } else {
                const suffix = mimeType.split("/")[1];
                const coverPath = `${UPLOAD_PATH}/img/${this.fileName}.${suffix}`;
                const coverUrl = `${UPLOAD_URL}img/${this.fileName}.${suffix}`;
                fs.writeFileSync(coverPath, file, "binary"); // 保存
                this.coverPath = `/img/${this.fileName}.${suffix}`;
                this.cover = coverUrl;
                resolve(this); // 处理完毕，退出
              }
            };
            // 解压文件 并 保存封面图片
            try {
              this.unzip(); // 加压缩 电子书
              this.parseContents(epub).then(({ chapters, chapterTree }) => {
                this.contents = chapters; // 目录items
                this.contentsTree = chapterTree;
                epub.getImage(cover, handleGetImage); //返回图片，以buffer形式
              }); // 找 TOC 属性
            } catch (error) {
              reject(error);
            }
          }
        }
      });
      // 解析
      epub.parse();
    });
  }

  // 返回 物理路径，检查path是否/开始
  static genPath(path) {
    if (path.startsWith("/")) {
      return `${UPLOAD_PATH}${path}`;
    } else {
      return `${UPLOAD_PATH}/${path}`;
    }
  }
  // 检查目录是否存在
  static pathExists(path) {
    // 如果是 相对路径，要补充成完整路径再检查
    if (path.startsWith(UPLOAD_PATH)) {
      return fs.existsSync(path);
    } else {
      return fs.existsSync(Book.genPath(path));
    }
  }

  static genCoverUrl(book) {
    const { cover } = book;
    if (cover) {
      if (cover.startsWith("/")) {
        return `${UPLOAD_URL}${cover}`;
      } else {
        return `${UPLOAD_URL}/${cover}`;
      }
    } else {
      return null;
    }
  }
  // 返回 contentsTree
  static genContentsTree(contents) {
    if (contents) {
      // 构造 书目录tree, 元素本身有label属性，不用添加
      const ContentsTree = [];
      contents.forEach((c) => {
        c.children = [];
        if (c.pid === "") {
          // 顶级节点，没有父节点
          ContentsTree.push(c);
        } else {
          // chapters里元素拷贝到chapterTree中，是浅拷贝，修改了chapters会影响chapterTree
          const parent = contents.find((_) => _.navId === c.pid);
          parent.children.push(c);
        }
      });
      return ContentsTree;
    }
  }

  //电子书解压缩
  unzip() {
    const AdmZip = require("adm-zip");
    const zip = new AdmZip(Book.genPath(this.path)); // 解析文件路径
    //加压缩
    zip.extractAllTo(
      /*target path*/ Book.genPath(this.unzipPath),
      /*overwrite*/ true
    );
  }

  parseContents(epub) {
    // 找到 电子书的目录文件
    function getNcxFilePath() {
      const spine = epub && epub.spine;
      const manifest = epub && epub.manifest;
      // console.log("spine", spine.toc);
      const ncx = spine.toc && spine.toc.href; // 取得toc的地址
      const id = spine.toc && spine.toc.id; //取得 toc的id => 可以到 manifest中取得文件地址
      // console.log("ncx",ncx,manifest[id].href);
      // spine有 没有toc有id的情况
      if (ncx) {
        return ncx;
      } else {
        // 如果没有地址，则根据id来获取
        return manifest[id].href;
      }
    }

    function findParent(array, level = 0, pid = "") {
      // console.log("findParent", array);
      return array.map((item) => {
        item.level = level; // 本层级别，从0开始
        item.pid = pid; // 父级的id
        if (item.navPoint && item.navPoint.length > 0) {
          // 有子元素叫 "navPoint" 并且 是数组，继续查询子目录
          item.navPoint = findParent(item.navPoint, level + 1, item["$"].id); //
        } else if (item.navPoint) {
          // 有子元素叫 "navPoint"， 是对象; 只有1个子元素，直接赋值
          item.navPoint.level = level + 1;
          item.navPoint.pid = item["$"].id;
        }
        return item;
      });
    }

    function flatten(array) {
      return [].concat(
        ...array.map((item) => {
          if (item.navPoint && item.navPoint.length > 0) {
            //navPoint 是数组
            return [].concat(item, ...flatten(item.navPoint));
          } else if (item.navPoint) {
            //是对象，返回两个元素
            return [].concat(item, item.navPoint);
          }
          return item;
        })
      );
    }

    const ncxFilePath = Book.genPath(`${this.unzipPath}/${getNcxFilePath()}`);
    // console.log("ncxFilePath",ncxFilePath);
    // 如果 toc 文件存在，解析该文件
    if (fs.existsSync(ncxFilePath)) {
      return new Promise((resolve, reject) => {
        const xml = fs.readFileSync(ncxFilePath, "utf-8"); //读取 toc.ncx 中的目录对象
        const dir = path.dirname(ncxFilePath).replace(UPLOAD_PATH, ""); // 获取ncx所在文件夹路径，因为，ncx里nav的路径是相对ncx的路径
        console.log("dir", dir);
        xml2js(
          xml,
          { explicitArray: false, ignoreAttrs: false }, // xml2js默认会把子子节点的值变为一个数组, explicitArray设置为false
          (err, json) => {
            if (err) {
              reject(err);
            } else {
              // console.log("xml", json);
              const navMap = json.ncx.navMap; // 文件根元素
              // console.log("navMap",JSON.stringify(navMap))
              if (navMap.navPoint && navMap.navPoint.length > 0) {
                navMap.navPoint = findParent(navMap.navPoint); // 查找是否有上级
                const newNavMap = flatten(navMap.navPoint); // 树状接口数组，转换为普通数组
                const chapters = [];
                // newNavMap 是数组化的 ncx
                newNavMap.forEach((chapter, index) => {
                  const src = chapter.content["$"].src;
                  chapter.id = `${src}`;
                  chapter.href = `${dir}/${src}`.replace(this.unzipPath, "");
                  // 目录的url链接
                  chapter.text = `${UPLOAD_URL}${dir}/${src}`; // url链接
                  // console.log("chapter.text", chapter);
                  // 目录的标题   取自 toc.navMap[index].navLabel.text
                  chapter.label = chapter.navLabel.text || "";
                  chapter.navId = chapter["$"].id;
                  chapter.fileName = this.fileName;
                  chapter.order = index + 1;
                  // console.log(chapter);
                  chapters.push(chapter);
                });
                // 构造 书目录tree, 元素本身有label属性，不用添加
                const chapterTree = Book.genContentsTree(chapters);
                resolve({ chapters, chapterTree }); // 返回 对象
                // console.log("chapters", chapters);
              } else {
                reject(new Error("目录解析失败，目录数为0"));
              }
            }
          }
        );
      });
    } else {
      throw new Error("目录对应的资源文件不存在");
    }
  }
  // 输出1个对象，用于保存进数据库，对象属性需要与数据库字段对应
  toDB() {
    return {
      fileName: this.fileName,
      cover: this.cover,
      title: this.title,
      author: this.author,
      publisher: this.publisher,
      fileName: this.bookId,
      language: this.language,
      rootFile: this.rootFile,
      originalName: this.originalName,
      // this.path = data.path || data.filePath;
      filePath: this.filePath,
      unzipPath: this.unzipPath,
      coverPath: this.coverPath,
      createUser: this.createUser,
      createDt: this.createDt,
      updateDt: this.updateDt,
      updateType: this.updateType,
      category: this.category,
      categoryText: this.categoryText,
    };
  }
  //删除加压后的物理文件
  reset() {
    if (Book.pathExists(this.filePath)) {
      console.log("删除epub文件...");
      fs.unlinkSync(Book.genPath(this.filePath));
    }
    if (Book.pathExists(this.coverPath)) {
      console.log("删除封面文件...");
      fs.unlinkSync(Book.genPath(this.coverPath));
    }
    if (Book.pathExists(this.unzipPath)) {
      console.log("删除解压目录...");
      fs.rmdirSync(Book.genPath(this.unzipPath), { recursive: true }); // 迭代删除
    }
  }
}

module.exports = Book;
